////////////////////////////////////////////////////////////////////////////////
// main.c
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////////////////////////
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include "strlib.h"

////////////////////////////////////////////////////////////////////////////////
// Global defines
////////////////////////////////////////////////////////////////////////////////
#define RET_SUCCESS  0
#define RET_FAILED   1
#define DEF_BUFF_LEN 512

////////////////////////////////////////////////////////////////////////////////
// Function prototypes
////////////////////////////////////////////////////////////////////////////////
int initWinsock();
int cleanWinsock();
SOCKET createServer(string hostName, string portNumber);
SOCKET createClient(SOCKET serverSocket);
void handleClient(SOCKET clientSocket);
string getFilename(string request);
string getFiletype(string filename);
string getMimetype(string filetype);

////////////////////////////////////////////////////////////////////////////////
// Entry point of application
////////////////////////////////////////////////////////////////////////////////
int main()
{
    SOCKET serverSocket = INVALID_SOCKET;
    SOCKET clientSocket = INVALID_SOCKET;
    
    // Init winsock
    if(initWinsock() == RET_FAILED)
        return RET_FAILED;

    // Create a server and check if it's valid
    serverSocket = createServer("localhost", "8080");
    if(serverSocket == INVALID_SOCKET)
        return RET_FAILED;

    while(TRUE)
    {
        // Wait for a client to connect and make sure it's valid
        clientSocket = createClient(serverSocket);
        if(clientSocket == INVALID_SOCKET)
            continue;

        // Process the client request
        handleClient(clientSocket);

        // Disconnect the client
        printf("\nClient disconnected.\n\n");
        closesocket(clientSocket);
    }
    
    // Clean up the application
    closesocket(serverSocket);
    cleanWinsock();

    return RET_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
// Initializes winsock. Returns RET_FAILED if failed, otherwise returns
// RET_SUCCESS
////////////////////////////////////////////////////////////////////////////////
int initWinsock()
{
    WSADATA wsaData;
    int result;

    // Initialize winsock
    result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if(result != 0)
    {
        printf("WSAStartup() failed: %d\n", result);
        getchar();
        return RET_FAILED;
    }

    return RET_SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
// Cleans winsock
////////////////////////////////////////////////////////////////////////////////
int cleanWinsock()
{
    WSACleanup();
    printf("Press any key to continue...\n");
    getchar();    
}

////////////////////////////////////////////////////////////////////////////////
// Create a server that is ready to accept new clients. If the function 
// fails INVALID_SOCKET is returned.
////////////////////////////////////////////////////////////////////////////////
SOCKET createServer(string hostName, string portNumber)
{
    SOCKET serverSocket = INVALID_SOCKET;
    int result;
    struct addrinfo* info;

    // Create a server and make sure it's valid
    serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(serverSocket == INVALID_SOCKET)
    {
        printf("socket() failed: Couldn't create server\n");
        getchar();
        return INVALID_SOCKET;
    }

    // Translate a host name to an ip-address and make sure it's valid
    result = getaddrinfo(hostName, portNumber, NULL, &info);
    if(result != 0)
    {
        printf("getaddrinfo() failed: %s\n", gai_strerror(result));
        getchar();
        return INVALID_SOCKET;
    }

    while(info->ai_family != AF_INET && info->ai_next != NULL)
        info = info->ai_next;

    // Bind the server socket to the address
    result = bind(serverSocket, info->ai_addr, info->ai_addrlen);
    if(result == SOCKET_ERROR)
    {
        printf("bind() failed %d\n", WSAGetLastError());
        getchar();
        return INVALID_SOCKET;
    }

    // Start listen for clients trying to connect
    result = listen(serverSocket, SOMAXCONN);
    if(result == SOCKET_ERROR)
    {
        printf("listen() failed %d\n", WSAGetLastError());
        getchar();
        return INVALID_SOCKET;
    }

    return serverSocket;
}

////////////////////////////////////////////////////////////////////////////////
// Wait for a client to connect to a server. If the function fails
// INVALID_SOCKET is returned.
////////////////////////////////////////////////////////////////////////////////
SOCKET createClient(SOCKET serverSocket)
{
    SOCKET clientSocket = INVALID_SOCKET;
    struct sockaddr clientAddr;
    int clientAddrLen;
    int result;
    char hostName[100];
    char portNumber[100];

    // Wait for a client to connect
    clientAddrLen = sizeof(clientAddr);
    clientSocket  = accept(serverSocket, &clientAddr, &clientAddrLen);        

    // Make sure the connected client is valid
    if(clientSocket == INVALID_SOCKET)
    {
        printf("accept() failed: %d\n", WSAGetLastError());
        getchar();
        return INVALID_SOCKET;
    }

    // Get the name of the client 
    result = getnameinfo(&clientAddr, clientAddrLen, hostName, 100, portNumber, 100, NI_NUMERICSERV);
    if(result != 0)
    {
        printf("getnameinfo() failed %d\n", WSAGetLastError());
        closesocket(clientSocket);
        getchar();
        return INVALID_SOCKET;
    }

    printf("Client connected: %s on port number %s.\n\n", hostName, portNumber);
    
    return clientSocket;
}

////////////////////////////////////////////////////////////////////////////////
// Process the client request
////////////////////////////////////////////////////////////////////////////////
void handleClient(SOCKET clientSocket)
{
    char buffer[512];
    int bytes;
    string filename;
    string filetype;
    string header;
    FILE* file;

    // Receive data from a client
    bytes = recv(clientSocket, buffer, 512, 0);
    buffer[bytes] = '\0';
    printf("%s\n", buffer);

    // Get the requested file name and file type
    filename = getFilename(buffer);
    filetype = getFiletype(filename);

    header = "HTTP/1.1 200 OK\r\nContent-type: ";
    header = Concat(header, getMimetype(filetype));
    header = Concat(header, "\r\n\n");

    // Send header info
    send(clientSocket, header, StringLength(header), 0);

    // Open the requested file
    file = fopen(filename, "rb");
    if(file)
    {
        // Read data from the file and send it to the server
        while(!feof(file))
        {            
            bytes = fread(buffer, sizeof(char), DEF_BUFF_LEN, file);
            send(clientSocket, buffer, bytes, 0);
        }

        fclose(file);
    }
    else 
    {
        // If the file wasn't found then send a 404-error
        char buffer[] = "<html>404 Not Found</html>";
        send(clientSocket, buffer, strlen(buffer), 0);
    }
}

////////////////////////////////////////////////////////////////////////////////
// Get the file name from the request. If the file doesn't exist then 
// index.html is returned.
////////////////////////////////////////////////////////////////////////////////
string getFilename(string request)
{
    int positionBegin = FindString("/", request, 0) + 1;
    int positionEnd   = FindString(" ", request, positionBegin + 1);
    string filename   = NULL;

    if(IthChar(request, positionBegin) == ' ')
        filename = "index.html";
    else 
        filename = SubString(request, positionBegin, positionEnd - 1);

    return filename;
}

////////////////////////////////////////////////////////////////////////////////
// Get the file type from a file name. The filename must be in the format:
// <filename.extension>
////////////////////////////////////////////////////////////////////////////////
string getFiletype(string filename)
{
    int positionBegin = FindString(".", filename, 0) + 1;

    return SubString(filename, positionBegin, StringLength(filename));
}

////////////////////////////////////////////////////////////////////////////////
// Get the correct mime type from the file type.
////////////////////////////////////////////////////////////////////////////////
string getMimetype(string filetype)
{
    if(StringEqual(filetype, "gif"))
        return "image/gif";
    else if(StringEqual(filetype, "jpg"))
        return "image/jpeg";
    else if(StringEqual(filetype, "html"))
        return "text/html";
    else 
        return "Mime type not supported...";
}